Skip to content

Add Slack as a TaskSpawner source (MVP)#1002

Open
jkahuja wants to merge 6 commits intokelos-dev:mainfrom
datagravity-ai:feat/slack-source-mvp
Open

Add Slack as a TaskSpawner source (MVP)#1002
jkahuja wants to merge 6 commits intokelos-dev:mainfrom
datagravity-ai:feat/slack-source-mvp

Conversation

@jkahuja
Copy link
Copy Markdown

@jkahuja jkahuja commented Apr 16, 2026

What type of PR is this?

/kind feature

What this PR does / why we need it:

Adds slack as a new source type in the TaskSpawner When struct, enabling
task creation from Slack messages. This is the minimum viable implementation
for API review — it includes the core triggering flow but defers status
reporting (posting updates back to Slack threads) to a follow-up PR.

What's included:

  • Slack API type with 2 fields: channels and mentionUserIDs
  • Centralized kelos-slack-server binary that connects to Slack via Socket Mode
    (outbound WebSocket — no ingress required) and routes messages to matching
    TaskSpawners
  • Message filtering: channel restrictions and @-mention requirements
  • Thread context fetching for follow-up messages within a Slack thread
  • Helm templates: Deployment, ServiceAccount, RBAC (ClusterRole/ClusterRoleBinding)
  • TaskSpawner controller updated to treat Slack as webhook-based (no spawner
    Deployment created)

What's NOT included:

This MVP does not post any status updates back to Slack — no "accepted",
"done", or "failed" messages appear in the thread. If barebones reporting
(plain text phase transitions) is desired in this PR, that's a small addition
(~170 lines). Otherwise, full rich reporting (Block Kit, live progress,
agent response rendering) will come in a follow-up PR.

Additional filtering coming in follow-up PRs (already implemented in datagravity-ai#75):

  • triggerCommand — slash command or message prefix (e.g., "/kelos", "!fix") that triggers task creation and is stripped from the prompt
  • allowedUsers — restrict which Slack user IDs can trigger tasks
  • excludeCommands — negative routing to prevent a spawner from firing on messages intended for another spawner

Which issue(s) this PR is related to:

Towards #932

Special notes for your reviewer:

This is carved out of a larger implementation (datagravity-ai#75) to keep the
API review focused.

Does this PR introduce a user-facing change?

Add Slack as a TaskSpawner source with a centralized kelos-slack-server that connects via Socket Mode and routes messages to matching agents.

Summary by cubic

Adds Slack as a TaskSpawner source via a centralized kelos-slack-server that uses Socket Mode to spawn tasks from Slack messages. Supports channel filtering, implicit @bot mention, regex triggers, and slash commands; preserves attachments in the task body and uses the first line of text as the task title; posting status back to Slack will come later.

  • New Features

    • New API: spec.when.slack with channels and triggers (RE2 patterns, OR semantics, per-trigger mentionOptional; empty pattern allowed). CRD validates channel IDs and trigger pattern max length.
    • Central kelos-slack-server: leader-elected Socket Mode listener (no ingress), Helm Deployment/ServiceAccount/RBAC; least-privilege RBAC to create tasks only; uses github.com/slack-go/slack.
    • Routing: channel filter; @mention required unless a trigger sets mentionOptional; slash commands bypass mention/trigger; thread replies include fetched context; top-level messages include attachments; titles use the first line of text.
    • Controller treats Slack like webhooks (no per-spawner Deployment).
  • Migration

    • Create a secret with SLACK_BOT_TOKEN and SLACK_APP_TOKEN; set .Values.slackServer.secretName, enable with .Values.slackServer.enabled=true, and configure the image if needed.
    • Invite the bot to the Slack channels to monitor.
    • Add when.slack to TaskSpawners. Use {} to fire on any bot mention, or set channels and triggers to narrow matching (an empty trigger pattern matches all messages).

Written for commit edd1133. Summary will update on new commits. Review in cubic

Adds a new `slack` source type to TaskSpawner that triggers task creation
from Slack messages via Socket Mode. A centralized kelos-slack-server
connects to Slack via an outbound WebSocket (no ingress required) and
routes messages to matching TaskSpawners based on channel restrictions
and @-mention requirements.

Includes Helm templates for deploying the slack server (Deployment,
ServiceAccount, RBAC) and updates the TaskSpawner controller to treat
Slack sources as webhook-based (no spawner Deployment created).

Reporting (posting status updates back to Slack threads) is deferred
to a follow-up PR.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Apr 16, 2026

CLA assistant check
All committers have signed the CLA.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 21 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="cmd/kelos-slack-server/Dockerfile">

<violation number="1" location="cmd/kelos-slack-server/Dockerfile:1">
P2: Base image is not pinned to an immutable digest/version, so builds can drift over time.</violation>
</file>

<file name="internal/manifests/charts/kelos/templates/rbac.yaml">

<violation number="1" location="internal/manifests/charts/kelos/templates/rbac.yaml:289">
P2: Slack server RBAC grants unnecessary Task update/watch access even though the Slack handler only creates Tasks.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread cmd/kelos-slack-server/Dockerfile
Comment thread internal/manifests/charts/kelos/templates/rbac.yaml
The slack handler only creates Tasks — the update and watch verbs were
left over from the reporting loop which is not included in this MVP.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gjkim42
Copy link
Copy Markdown
Collaborator

gjkim42 commented Apr 27, 2026

/kelos api-review

kelos-bot[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Collaborator

@gjkim42 gjkim42 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API Design Review

Verdict: COMMENT
Scope: api/v1alpha1/taskspawner_types.goSlack source

Desired shape

type Slack struct {
    // The bot must be invited to each channel; this is a post-delivery
    // filter, not a privacy scope. Empty = every invited channel.
    // +optional
    // +kubebuilder:validation:MaxItems=64
    // +kubebuilder:validation:items:Pattern=`^[CG][A-Z0-9]{8,}$`
    Channels []string `json:"channels,omitempty"`

    // Bot mention is implicitly required. Pattern triggers add a regex
    // AND'd with the mention. Multiple triggers OR. Empty = every bot
    // mention fires.
    // +optional
    // +kubebuilder:validation:MaxItems=8
    Triggers []SlackTrigger `json:"triggers,omitempty"`
}

type SlackTrigger struct {
    // Go RE2 regex against message text. Unanchored.
    // +kubebuilder:validation:Required
    // +kubebuilder:validation:MaxLength=256
    Pattern string `json:"pattern"`
}

Smallest valid spawner:

  spec:
    when:
      slack: {}

fires on any bot mention in any invited channel.

Copy link
Copy Markdown
Collaborator

@gjkim42 gjkim42 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reviewed from an API perspective, which is a minimal and expandable API.
What do you think?

@knechtionscoding
Copy link
Copy Markdown
Contributor

@gjkim42 I like the idea of it listening for itself automatically. But it is also helpful to not require a specific mention, if you want to trigger on some other text then an @ specifically? Might not be worth adding right now?

@gjkim42
Copy link
Copy Markdown
Collaborator

gjkim42 commented Apr 27, 2026

Then how about adding a field for make it optional?

mentionOptional *bool to SlackTrigger?

@knechtionscoding
Copy link
Copy Markdown
Contributor

I think that is reasonable

…er patterns

Reshape the Slack source API per review feedback:
- Remove MentionUserIDs field; bot mention is now implicit (the handler
  knows its own user ID from Slack auth)
- Add Triggers []SlackTrigger with RE2 regex patterns (OR semantics)
- Add MentionOptional *bool per trigger to fire on pattern alone
- Add kubebuilder validation markers (MaxItems, Pattern, MaxLength)

Simplest valid config is now `slack: {}` which fires on any bot mention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 8 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="api/v1alpha1/taskspawner_types.go">

<violation number="1" location="api/v1alpha1/taskspawner_types.go:528">
P1: Slack trigger Pattern field lacks MinLength validation, allowing an empty regex pattern that matches every message and can cause runaway task spawning</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread api/v1alpha1/taskspawner_types.go Outdated
Prevents empty regex patterns that would match every message.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jkahuja
Copy link
Copy Markdown
Author

jkahuja commented Apr 28, 2026

@gjkim42 thanks for your feedback! I addressed your comment

Copy link
Copy Markdown
Collaborator

@gjkim42 gjkim42 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you need to sign the CLA.

Comment thread api/v1alpha1/taskspawner_types.go Outdated
Allow empty patterns so triggers can match all messages and remain
forward-compatible with future trigger conditions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread api/v1alpha1/taskspawner_types.go Outdated
@gjkim42
Copy link
Copy Markdown
Collaborator

gjkim42 commented Apr 29, 2026

/kelos review

Copy link
Copy Markdown

@kelos-bot kelos-bot Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Kelos Reviewer Agent @gjkim42

Review Summary

Verdict: APPROVE
Overall correctness: patch is correct
Scope: Adds Slack as a TaskSpawner source via a centralized kelos-slack-server (Socket Mode) with channel filters, regex triggers, optional bot mention, slash-command handling, and thread-context fetching.

Findings Overview

Priority Count File:Line Summary
P0 0 none
P1 0 none
P2 1 internal/slack/handler.go:233-236 Top-level messages silently drop attachments — enriched Body is overwritten with msg.Text
P3 2 internal/slack/filter.go:86 Title template variable is the user name, diverges from other sources
P3 internal/slack/filter.go:40-52 Trigger regexp cache has no eviction policy

Findings

Correctness

  • [P2] internal/slack/handler.go:233-236enrichMessage carefully merges message text and formatAttachments(...) into SlackMessageData.Body, but routeMessage then overrides taskMsg.Body = msg.Text for any non-thread message, throwing the attachments away before ExtractSlackWorkItem reads msg.Body. The result: a top-level Slack message that says <@bot> investigate this and forwards another message as an attachment will reach the agent prompt with only the investigate this line — the forwarded content is lost. Either drop the override here (use the enriched Body for top-level messages too) or remove the attachment merging in enrichMessage as dead code. The handler test top-level message uses raw text as body happens to set Text == Body so it does not catch this; consider extending it with an attachment-only top-level case.

Code Quality / Suggestions

  • [P3] internal/slack/filter.go:86ExtractSlackWorkItem maps Title to msg.UserName, while other sources (GitHub issue/PR, Jira, Linear) populate Title with the actual work-item title. Users templating {{ .Title }} across sources will get inconsistent semantics. Consider mapping to the first line of the message text or documenting the divergence in the godoc on When.Slack. (Already raised by the API reviewer agent — re-flagging for visibility, non-blocking.)
  • [P3] internal/slack/filter.go:40-52triggerRegexpCache (a sync.Map) is populated unconditionally and never evicted. With trigger patterns being user-controlled and TaskSpawners coming and going, the cache grows for the lifetime of the slack-server pod. Bounded by MaxItems=8 triggers per spawner * (currently configured + historical) spawners — not an immediate concern but worth a TODO if long-lived deployments churn many spawners.

Key takeaways

  • API surface, validation, generated artifacts (CRDs, deepcopy), Helm chart, and webhook-based controller behavior all look consistent with the existing webhook-server pattern; controller test for isWebhookBased is appropriately extended.
  • Logging conventions, RBAC scope (least-privilege after the recent commit dropped update/watch on tasks), least-privilege container security context, and leader-elected Socket Mode runnable look correct.
  • Only meaningful concern is the top-level message body override that drops attachments; resolve before users hit it in the wild, but it is not a release blocker.

Comment thread internal/slack/handler.go Outdated
Comment thread internal/slack/filter.go Outdated
Comment thread internal/slack/filter.go
- Make SlackTrigger Pattern optional to allow future trigger conditions
- Fix Body override in routeMessage that silently dropped attachments
  for top-level messages
- Map Title to first line of message text instead of UserName for
  consistency with other sources

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 29, 2026

Greptile Summary

This PR adds Slack as a TaskSpawner source via a centralized kelos-slack-server binary that connects using Socket Mode (no ingress needed) and routes messages to matching TaskSpawners based on channel, @mention, and regex trigger filters. The overall design is solid — the Helm templates, RBAC, API types, deepcopy, and filter logic are all clean — but two correctness issues in handler.go need attention before merging.

  • Thread reply silent drop: when FetchThreadContext fails, handleEventsAPI returns early without calling routeMessage, silently losing the message. Since thread context is enrichment, it should fall back to the already-populated msg.Body on error.
  • Task name collision for long spawner names: taskName is formatted as <spawnerName>-slack-<12-char hash> and truncated to 63 chars. For spawner names longer than 44 characters, the hash suffix is partially or fully removed, causing different Slack messages from the same spawner to produce identical task names and be silently dropped via the IsAlreadyExists path.

Confidence Score: 4/5

Two P1 correctness issues in handler.go should be fixed before merging — both can cause silent task loss.

The API types, Helm templates, RBAC, filter logic, and controller change are all correct and well-tested. The two P1 issues (thread reply drop on fetch error and task name collision for long spawner names) are straightforward fixes and don't affect the broader architecture.

internal/slack/handler.go — thread reply early return and task name truncation logic

Important Files Changed

Filename Overview
internal/slack/handler.go Core Socket Mode handler: two P1 issues — thread reply dropped on fetch error instead of falling back, and task name truncation causes collisions for spawner names > 44 chars
internal/slack/filter.go Channel/mention/trigger matching logic; well-tested, correct handling of partial-ID mention detection, empty patterns, and slash command bypass
internal/slack/thread.go Thread context fetching and formatting; BotParticipated is exported but never called in the handler
cmd/kelos-slack-server/main.go Binary entrypoint; correctly sets up controller-runtime manager with leader election and health probes
api/v1alpha1/taskspawner_types.go Adds Slack and SlackTrigger API types with kubebuilder validation markers; deepcopy generated correctly
internal/manifests/charts/kelos/templates/slack-server.yaml Helm Deployment with least-privilege security context, health probes, and secret-backed env vars; image tag interpolation is correct
internal/manifests/charts/kelos/templates/rbac.yaml Least-privilege ClusterRole granting only get/list/watch on taskspawners and create/get/list on tasks
internal/controller/taskspawner_controller.go Correctly treats Slack as webhook-based (no per-spawner Deployment); single-line change with test coverage

Sequence Diagram

sequenceDiagram
    participant Slack
    participant SlackHandler
    participant Filter
    participant TaskBuilder
    participant K8s API

    Slack->>SlackHandler: Socket Mode event (message / slash command)
    SlackHandler->>SlackHandler: Ack event
    SlackHandler->>SlackHandler: shouldProcess() filter
    SlackHandler->>Slack: GetUserInfoContext + GetPermalinkContext
    Slack-->>SlackHandler: user info + permalink

    alt Thread reply
        SlackHandler->>Slack: GetConversationRepliesContext
        Slack-->>SlackHandler: thread messages
        SlackHandler->>SlackHandler: FormatThreadContext → msg.Body
    end

    SlackHandler->>K8s API: List TaskSpawners (all namespaces)
    K8s API-->>SlackHandler: TaskSpawnerList

    loop For each Slack TaskSpawner
        SlackHandler->>Filter: MatchesSpawner(slackCfg, msg, botUserID)
        Filter-->>SlackHandler: matched / not matched
        alt Matches
            SlackHandler->>TaskBuilder: BuildTask(spawner, templateVars)
            TaskBuilder-->>SlackHandler: Task object
            SlackHandler->>K8s API: Create Task
            K8s API-->>SlackHandler: Created / AlreadyExists
        end
    end
Loading

Comments Outside Diff (3)

  1. internal/slack/handler.go, line 1424-1432 (link)

    P1 Thread reply silently dropped on transient fetch failure

    If FetchThreadContext returns an error (e.g. Slack API rate-limit, transient network error), return is hit before h.routeMessage is called, so the mention is silently dropped — the user gets no task and no feedback. Since thread context is enrichment rather than a hard requirement, it is safer to fall back to the already-enriched msg.Body and continue routing.

    if innerEvent.ThreadTimeStamp != "" {
        body, err := FetchThreadContext(ctx, h.api, innerEvent.Channel, innerEvent.ThreadTimeStamp, h.botUserID)
        if err != nil {
            h.log.Error(err, "Failed to fetch thread context, falling back to message text",
                "channel", innerEvent.Channel, "threadTS", innerEvent.ThreadTimeStamp)
            // fall through: msg.Body already contains the raw message text
        } else {
            msg.Body = body
            msg.HasThreadContext = true
        }
    }
    
    h.routeMessage(ctx, msg)
    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: internal/slack/handler.go
    Line: 1424-1432
    
    Comment:
    **Thread reply silently dropped on transient fetch failure**
    
    If `FetchThreadContext` returns an error (e.g. Slack API rate-limit, transient network error), `return` is hit before `h.routeMessage` is called, so the mention is silently dropped — the user gets no task and no feedback. Since thread context is enrichment rather than a hard requirement, it is safer to fall back to the already-enriched `msg.Body` and continue routing.
    
    ```go
    if innerEvent.ThreadTimeStamp != "" {
        body, err := FetchThreadContext(ctx, h.api, innerEvent.Channel, innerEvent.ThreadTimeStamp, h.botUserID)
        if err != nil {
            h.log.Error(err, "Failed to fetch thread context, falling back to message text",
                "channel", innerEvent.Channel, "threadTS", innerEvent.ThreadTimeStamp)
            // fall through: msg.Body already contains the raw message text
        } else {
            msg.Body = body
            msg.HasThreadContext = true
        }
    }
    
    h.routeMessage(ctx, msg)
    ```
    
    How can I resolve this? If you propose a fix, please make it concise.
  2. internal/slack/handler.go, line 1541-1551 (link)

    P1 Task name collision for spawner names longer than 44 characters

    taskName is formatted as <spawnerName>-slack-<12-char hash>. When spawner.Name exceeds 44 characters the combined string exceeds 63 characters and is truncated, chopping off part (or all) of the shortHash. Two different Slack messages from the same spawner then produce the same taskName; the second message is silently discarded via the IsAlreadyExists path.

    Use the hash to derive the task name rather than truncating, e.g.:

    combined := fmt.Sprintf("%s-slack-%s", spawner.Name, shortHash)
    if len(combined) > 63 {
        // Re-hash the full combined string to a fixed-width name that is always unique
        fullSum := sha256.Sum256([]byte(combined))
        taskName = "slack-" + hex.EncodeToString(fullSum[:])[:12]
    } else {
        taskName = combined
    }
    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: internal/slack/handler.go
    Line: 1541-1551
    
    Comment:
    **Task name collision for spawner names longer than 44 characters**
    
    `taskName` is formatted as `<spawnerName>-slack-<12-char hash>`. When `spawner.Name` exceeds 44 characters the combined string exceeds 63 characters and is truncated, chopping off part (or all) of the `shortHash`. Two different Slack messages from the same spawner then produce the same `taskName`; the second message is silently discarded via the `IsAlreadyExists` path.
    
    Use the hash to derive the task name rather than truncating, e.g.:
    
    ```go
    combined := fmt.Sprintf("%s-slack-%s", spawner.Name, shortHash)
    if len(combined) > 63 {
        // Re-hash the full combined string to a fixed-width name that is always unique
        fullSum := sha256.Sum256([]byte(combined))
        taskName = "slack-" + hex.EncodeToString(fullSum[:])[:12]
    } else {
        taskName = combined
    }
    ```
    
    How can I resolve this? If you propose a fix, please make it concise.
  3. internal/slack/thread.go, line 1883-1890 (link)

    P2 BotParticipated is exported but never called

    BotParticipated is defined and exported but is not referenced anywhere in this PR's handler code. If it is meant to gate thread-reply processing (preventing the bot from responding to threads it never joined), it should be wired into handleEventsAPI. Otherwise it is dead code and should be unexported or removed to keep the package surface minimal.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: internal/slack/thread.go
    Line: 1883-1890
    
    Comment:
    **`BotParticipated` is exported but never called**
    
    `BotParticipated` is defined and exported but is not referenced anywhere in this PR's handler code. If it is meant to gate thread-reply processing (preventing the bot from responding to threads it never joined), it should be wired into `handleEventsAPI`. Otherwise it is dead code and should be unexported or removed to keep the package surface minimal.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: internal/slack/handler.go
Line: 1424-1432

Comment:
**Thread reply silently dropped on transient fetch failure**

If `FetchThreadContext` returns an error (e.g. Slack API rate-limit, transient network error), `return` is hit before `h.routeMessage` is called, so the mention is silently dropped — the user gets no task and no feedback. Since thread context is enrichment rather than a hard requirement, it is safer to fall back to the already-enriched `msg.Body` and continue routing.

```go
if innerEvent.ThreadTimeStamp != "" {
    body, err := FetchThreadContext(ctx, h.api, innerEvent.Channel, innerEvent.ThreadTimeStamp, h.botUserID)
    if err != nil {
        h.log.Error(err, "Failed to fetch thread context, falling back to message text",
            "channel", innerEvent.Channel, "threadTS", innerEvent.ThreadTimeStamp)
        // fall through: msg.Body already contains the raw message text
    } else {
        msg.Body = body
        msg.HasThreadContext = true
    }
}

h.routeMessage(ctx, msg)
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: internal/slack/handler.go
Line: 1541-1551

Comment:
**Task name collision for spawner names longer than 44 characters**

`taskName` is formatted as `<spawnerName>-slack-<12-char hash>`. When `spawner.Name` exceeds 44 characters the combined string exceeds 63 characters and is truncated, chopping off part (or all) of the `shortHash`. Two different Slack messages from the same spawner then produce the same `taskName`; the second message is silently discarded via the `IsAlreadyExists` path.

Use the hash to derive the task name rather than truncating, e.g.:

```go
combined := fmt.Sprintf("%s-slack-%s", spawner.Name, shortHash)
if len(combined) > 63 {
    // Re-hash the full combined string to a fixed-width name that is always unique
    fullSum := sha256.Sum256([]byte(combined))
    taskName = "slack-" + hex.EncodeToString(fullSum[:])[:12]
} else {
    taskName = combined
}
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: internal/slack/thread.go
Line: 1883-1890

Comment:
**`BotParticipated` is exported but never called**

`BotParticipated` is defined and exported but is not referenced anywhere in this PR's handler code. If it is meant to gate thread-reply processing (preventing the bot from responding to threads it never joined), it should be wired into `handleEventsAPI`. Otherwise it is dead code and should be unexported or removed to keep the package surface minimal.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(api): Address PR review feedback for..." | Re-trigger Greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/feature Categorizes issue or PR as related to a new feature needs-actor needs-priority needs-triage release-note

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants